import { Camera } from '../cameras/Camera.js';
import { Vector3 } from '../math/Vector3.js';
import { LineSegments } from '../objects/LineSegments.js';
import { Color } from '../math/Color.js';
import { LineBasicMaterial } from '../materials/LineBasicMaterial.js';
import { BufferGeometry } from '../core/BufferGeometry.js';
import { Float32BufferAttribute } from '../core/BufferAttribute.js';

const _vector = /* @__PURE__ */ new Vector3();
const _camera = /* @__PURE__ */ new Camera();

/**
 *	- shows frustum, line of sight and up of the camera
 *	- suitable for fast updates
 * 	- based on frustum visualization in lightgl.js shadowmap example
 *		http://evanw.github.com/lightgl.js/tests/shadowmap.html
 */

class CameraHelper extends LineSegments {
  constructor(camera) {
    const geometry = new BufferGeometry();
    const material = new LineBasicMaterial({ color: 0xffffff, vertexColors: true, toneMapped: false });

    const vertices = [];
    const colors = [];

    const pointMap = {};

    // colors

    const colorFrustum = new Color(0xffaa00);
    const colorCone = new Color(0xff0000);
    const colorUp = new Color(0x00aaff);
    const colorTarget = new Color(0xffffff);
    const colorCross = new Color(0x333333);

    // near

    addLine('n1', 'n2', colorFrustum);
    addLine('n2', 'n4', colorFrustum);
    addLine('n4', 'n3', colorFrustum);
    addLine('n3', 'n1', colorFrustum);

    // far

    addLine('f1', 'f2', colorFrustum);
    addLine('f2', 'f4', colorFrustum);
    addLine('f4', 'f3', colorFrustum);
    addLine('f3', 'f1', colorFrustum);

    // sides

    addLine('n1', 'f1', colorFrustum);
    addLine('n2', 'f2', colorFrustum);
    addLine('n3', 'f3', colorFrustum);
    addLine('n4', 'f4', colorFrustum);

    // cone

    addLine('p', 'n1', colorCone);
    addLine('p', 'n2', colorCone);
    addLine('p', 'n3', colorCone);
    addLine('p', 'n4', colorCone);

    // up

    addLine('u1', 'u2', colorUp);
    addLine('u2', 'u3', colorUp);
    addLine('u3', 'u1', colorUp);

    // target

    addLine('c', 't', colorTarget);
    addLine('p', 'c', colorCross);

    // cross

    addLine('cn1', 'cn2', colorCross);
    addLine('cn3', 'cn4', colorCross);

    addLine('cf1', 'cf2', colorCross);
    addLine('cf3', 'cf4', colorCross);

    function addLine(a, b, color) {
      addPoint(a, color);
      addPoint(b, color);
    }

    function addPoint(id, color) {
      vertices.push(0, 0, 0);
      colors.push(color.r, color.g, color.b);

      if (pointMap[id] === undefined) {
        pointMap[id] = [];
      }

      pointMap[id].push(vertices.length / 3 - 1);
    }

    geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    geometry.setAttribute('color', new Float32BufferAttribute(colors, 3));

    super(geometry, material);

    this.type = 'CameraHelper';

    this.camera = camera;
    if (this.camera.updateProjectionMatrix) this.camera.updateProjectionMatrix();

    this.matrix = camera.matrixWorld;
    this.matrixAutoUpdate = false;

    this.pointMap = pointMap;

    this.update();
  }

  update() {
    const geometry = this.geometry;
    const pointMap = this.pointMap;

    const w = 1;
    const h = 1;

    // we need just camera projection matrix inverse
    // world matrix must be identity

    _camera.projectionMatrixInverse.copy(this.camera.projectionMatrixInverse);

    // center / target

    setPoint('c', pointMap, geometry, _camera, 0, 0, -1);
    setPoint('t', pointMap, geometry, _camera, 0, 0, 1);

    // near

    setPoint('n1', pointMap, geometry, _camera, -w, -h, -1);
    setPoint('n2', pointMap, geometry, _camera, w, -h, -1);
    setPoint('n3', pointMap, geometry, _camera, -w, h, -1);
    setPoint('n4', pointMap, geometry, _camera, w, h, -1);

    // far

    setPoint('f1', pointMap, geometry, _camera, -w, -h, 1);
    setPoint('f2', pointMap, geometry, _camera, w, -h, 1);
    setPoint('f3', pointMap, geometry, _camera, -w, h, 1);
    setPoint('f4', pointMap, geometry, _camera, w, h, 1);

    // up

    setPoint('u1', pointMap, geometry, _camera, w * 0.7, h * 1.1, -1);
    setPoint('u2', pointMap, geometry, _camera, -w * 0.7, h * 1.1, -1);
    setPoint('u3', pointMap, geometry, _camera, 0, h * 2, -1);

    // cross

    setPoint('cf1', pointMap, geometry, _camera, -w, 0, 1);
    setPoint('cf2', pointMap, geometry, _camera, w, 0, 1);
    setPoint('cf3', pointMap, geometry, _camera, 0, -h, 1);
    setPoint('cf4', pointMap, geometry, _camera, 0, h, 1);

    setPoint('cn1', pointMap, geometry, _camera, -w, 0, -1);
    setPoint('cn2', pointMap, geometry, _camera, w, 0, -1);
    setPoint('cn3', pointMap, geometry, _camera, 0, -h, -1);
    setPoint('cn4', pointMap, geometry, _camera, 0, h, -1);

    geometry.getAttribute('position').needsUpdate = true;
  }

  dispose() {
    this.geometry.dispose();
    this.material.dispose();
  }
}

function setPoint(point, pointMap, geometry, camera, x, y, z) {
  _vector.set(x, y, z).unproject(camera);

  const points = pointMap[point];

  if (points !== undefined) {
    const position = geometry.getAttribute('position');

    for (let i = 0, l = points.length; i < l; i++) {
      position.setXYZ(points[i], _vector.x, _vector.y, _vector.z);
    }
  }
}

export { CameraHelper };
